/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.boot.redis.manager;

import cn.hutool.core.date.DatePattern;
import com.iwindplus.boot.domain.constant.CommonConstant.ExceptionConstant;
import com.iwindplus.boot.domain.enums.BizCodeEnum;
import com.iwindplus.boot.domain.exception.BizException;
import com.iwindplus.boot.redis.domain.enums.RedissonLockType;
import com.iwindplus.boot.redis.domain.property.RedisProperty;
import com.iwindplus.boot.redis.domain.property.RedisProperty.LockConfig;
import com.iwindplus.boot.util.DatesUtil;
import jakarta.annotation.Resource;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RDeque;
import org.redisson.api.RList;
import org.redisson.api.RLock;
import org.redisson.api.RMap;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RScoredSortedSet;
import org.redisson.api.RSet;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;

/**
 * Redisson 数据操作管理器.
 * <p>
 * 支持 Runnable、Supplier、Function、Consumer<T> 四种函数式编程接口调用方式， 提供统一的加锁执行模板，适用于分布式环境下线程安全操作场景
 * <p>
 * 支持 String、Hash、List、Set、ZSet 等常见数据结构的操作
 *
 * @author zengdegui
 * @since 2018/9/5
 */
@Slf4j
public class RedissonManager {

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private RedisProperty redisProperty;

    /**
     * 存储字符串值，并设置过期时间.
     *
     * @param key      键名
     * @param value    要存储的字符串值
     * @param duration 过期时间（支持负数或零表示永不过期）
     */
    public void set(String key, String value, Duration duration) {
        execute(() -> {
            redissonClient.getBucket(key).set(value, duration);
            return null;
        });
    }

    /**
     * 获取指定键的字符串值.
     *
     * @param key 键名
     * @return String
     */
    public String get(String key) {
        return execute(() -> redissonClient.<String>getBucket(key).get());
    }

    /**
     * 向 Hash 表中设置字段值.
     *
     * @param key     Hash 键名
     * @param hashKey Hash 中的具体字段名
     * @param value   要设置的值（支持泛型）
     */
    public <T> void hSet(String key, String hashKey, T value) {
        execute(() -> {
            RMap<String, T> map = redissonClient.getMap(key);
            map.put(hashKey, value);
            return null;
        });
    }

    /**
     * 从 Hash 表中获取指定字段的值.
     *
     * @param key     Hash 键名
     * @param hashKey Hash 中的具体字段名
     * @param <T>     泛型
     * @return T
     */

    public <T> T hGet(String key, String hashKey) {
        return execute(() -> {
            RMap<String, T> map = redissonClient.getMap(key);
            return map.get(hashKey);
        });
    }

    /**
     * 获取整个 Hash 表.
     *
     * @param key Hash 键名
     * @param <T> 泛型
     * @return Map
     */
    public <T> Map<String, T> hGetAll(String key) {
        return execute(() -> redissonClient.getMap(key));
    }

    /**
     * 向列表左侧插入一个或多个元素.
     *
     * @param key    列表键名
     * @param values 要插入的一个或多个元素
     */
    public void lPush(String key, Object... values) {
        execute(() -> {
            RList<Object> list = redissonClient.getList(key);
            list.addAll(0, List.of(values));
            return null;
        });
    }

    /**
     * 向列表右侧插入一个或多个元素.
     *
     * @param key    列表键名
     * @param values 要插入的一个或多个元素
     */
    public void rPush(String key, Object... values) {
        execute(() -> {
            RList<Object> list = redissonClient.getList(key);
            list.addAll(List.of(values));
            return null;
        });
    }

    /**
     * 从列表左侧弹出一个元素.
     *
     * @param key 列表键名
     * @return Object
     */
    public Object lPop(String key) {
        return execute(() -> {
            RDeque<Object> deque = redissonClient.getDeque(key);
            return deque.poll();
        });
    }

    /**
     * 从列表右侧弹出一个元素.
     *
     * @param key 列表键名
     * @return Object
     */
    public Object rpop(String key) {
        return execute(() -> {
            RDeque<Object> deque = redissonClient.getDeque(key);
            return deque.pollLast();
        });
    }

    /**
     * 获取列表中 [start, end) 范围内的元素.
     *
     * @param key   列表键名
     * @param start 起始索引（包含）
     * @param end   结束索引（不包含）
     * @return List<Object>
     */
    public List<Object> lRange(String key, int start, int end) {
        return execute(() -> {
            RList<Object> list = redissonClient.getList(key);
            int size = list.size();
            if (start >= size || end <= 0 || start >= end) {
                return List.of();
            }
            return list.subList(start, Math.min(end, size));
        });
    }

    /**
     * 向集合中添加一个或多个元素.
     *
     * @param key    集合键名
     * @param values 要添加的一个或多个元素
     */
    public void sAdd(String key, Object... values) {
        execute(() -> {
            RSet<Object> set = redissonClient.getSet(key);
            set.addAll(List.of(values));
            return null;
        });
    }

    /**
     * 获取集合中的所有元素.
     *
     * @param key 集合键名
     * @return Set<Object>
     */
    public Set<Object> sMembers(String key) {
        return execute(() -> redissonClient.getSet(key));
    }

    /**
     * 从集合中删除一个或多个元素.
     *
     * @param key    集合键名
     * @param values 要删除的一个或多个元素
     */
    public void sRem(String key, Object... values) {
        execute(() -> {
            RSet<Object> set = redissonClient.getSet(key);
            set.removeAll(List.of(values));
            return null;
        });
    }

    /**
     * 向有序集合中添加一个元素，并指定其分数.
     *
     * @param key   有序集合键名
     * @param score 元素的排序分值
     * @param value 要添加的元素
     */
    public void zAdd(String key, double score, Object value) {
        execute(() -> {
            RScoredSortedSet<Object> zSet = redissonClient.getScoredSortedSet(key);
            zSet.add(score, value);
            return null;
        });
    }

    /**
     * 获取有序集合中 [start, end] 范围内的元素.
     *
     * @param key   有序集合键名
     * @param start 起始索引（包含）
     * @param end   结束索引（包含）
     * @return Collection<Object>
     */
    public Collection<Object> zRange(String key, int start, int end) {
        return execute(() -> {
            RScoredSortedSet<Object> zSet = redissonClient.getScoredSortedSet(key);
            return zSet.valueRange(start, end).stream().toList();
        });
    }

    /**
     * 从有序集合中删除一个或多个元素.
     *
     * @param key    有序集合键名
     * @param values 要删除的一个或多个元素
     */
    public void zRem(String key, Object... values) {
        execute(() -> {
            RScoredSortedSet<Object> zSet = redissonClient.getScoredSortedSet(key);
            zSet.removeAll(List.of(values));
            return null;
        });
    }

    /**
     * 删除指定的键.
     *
     * @param key 要删除的键名
     */
    public void delete(String key) {
        execute(() -> {
            redissonClient.getKeys().delete(key);
            return null;
        });
    }

    /**
     * 判断指定键是否存在.
     *
     * @param key 键名
     * @return 若存在返回 true，否则返回 false
     */
    public boolean exists(String key) {
        return execute(() -> redissonClient.getKeys().countExists(key) > 0);
    }

    /**
     * 设置键的过期时间.
     *
     * @param key      键名
     * @param duration 过期时间（支持负数或零表示永不过期）
     */
    public void expire(String key, Duration duration) {
        execute(() -> {
            redissonClient.getKeys().expire(key, duration.toSeconds(), TimeUnit.SECONDS);
            return null;
        });
    }

    /**
     * 获取键的剩余生存时间（若未设置过期时间则返回 -1，若键不存在则返回 -2）.
     *
     * @param key 键名
     * @return long
     */
    public long ttl(String key) {
        return execute(() -> redissonClient.getKeys().remainTimeToLive(key));
    }

    /**
     * 获取分布式锁对象.
     *
     * @param lockType 锁类型
     * @param lockKey  锁的键
     * @return RLock 分布式锁对象
     */
    public RLock getLock(RedissonLockType lockType, String lockKey) {
        switch (lockType) {
            case LOCK:
                return redissonClient.getLock(lockKey);
            case SPIN_LOCK:
                return redissonClient.getSpinLock(lockKey);
            case FENCED_LOCK:
                return redissonClient.getFencedLock(lockKey);
            case FAIR_LOCK:
                return redissonClient.getFairLock(lockKey);
            case READ_LOCK:
                return redissonClient.getReadWriteLock(lockKey).readLock();
            case WRITE_LOCK:
                return redissonClient.getReadWriteLock(lockKey).writeLock();
            default:
                throw new BizException(BizCodeEnum.UNSUPPORTED_LOCK_TYPE, new Object[]{lockKey});
        }
    }

    /**
     * 尝试获取分布式锁。
     *
     * @param lock      锁对象
     * @param waitTime  等待获取锁的最大时间
     * @param leaseTime 锁的持有时间
     * @param unit      时间单位
     * @return boolean 是否成功获取锁
     */
    public boolean tryLock(RLock lock, long waitTime, long leaseTime, TimeUnit unit) {
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            log.warn("尝试获取分布式锁时被中断，lockKey={}", lock.getName(), ex);
            return false;
        }
    }

    /**
     * 尝试获取限流许可.
     *
     * @param name         限流器名称（key）
     * @param rateType     限流类型（如 OVERALL, PER_CLIENT）
     * @param rate         限流阈值
     * @param rateInterval 限流时间窗口
     * @return boolean
     */
    public boolean tryAcquire(String name, RateType rateType, Long rate, Duration rateInterval) {
        final RRateLimiter rateLimiter = this.redissonClient.getRateLimiter(name);

        // 只有在限流器不存在时才初始化
        if (!rateLimiter.isExists()) {
            if (!rateLimiter.trySetRate(rateType, rate, rateInterval)) {
                log.warn("限流器初始化失败，name={}", name);
                throw new BizException(BizCodeEnum.RATE_LIMITER_INIT_ERROR, new Object[]{name});
            }

            rateLimiter.expireIfNotSet(rateInterval);
        }

        boolean acquired = rateLimiter.tryAcquire();
        log.info("尝试获取限流许可，name={}, acquired={}", name, acquired);

        if (!acquired) {
            log.warn("限流许可获取失败，name={}", name);
            throw new BizException(BizCodeEnum.REQUEST_TOO_FAST, new Object[]{name});
        }

        return true;
    }

    /**
     * 释放指定的分布式锁。
     *
     * @param lock 要释放的锁对象
     */
    public void unlock(RLock lock) {
        execute(() -> {
            if (this.isHeldByCurrentThread(lock)) {
                lock.unlock();
            }
            return null;
        });
    }

    /**
     * 判断当前线程是否持有该锁。
     *
     * @param lock 锁对象
     * @return boolean 是否持有锁
     */
    public boolean isHeldByCurrentThread(RLock lock) {
        return lock != null && lock.isHeldByCurrentThread();
    }

    /**
     * 获取或创建一个原子计数器.
     *
     * @param key 计数器键
     * @return RAtomicLong 原子计数器
     */
    public RAtomicLong getAtomicCounter(String key) {
        return redissonClient.getAtomicLong(key);
    }

    /**
     * 递增指定键的计数器，并返回新值.
     *
     * @param key 计数器键
     * @return long 新的计数值
     */
    public long incrementAndGet(String key) {
        return execute(() -> {
            RAtomicLong counter = getAtomicCounter(key);
            return counter.incrementAndGet();
        });
    }

    /**
     * 递增指定键的计数器，并设置过期时间.
     *
     * @param key      计数器键
     * @param duration 过期时间（支持负数或零表示永不过期）
     * @return long 新的计数值
     */
    public long incrementAndGetWithExpire(String key, Duration duration) {
        return execute(() -> {
            RAtomicLong counter = getAtomicCounter(key);
            long value = counter.incrementAndGet();
            counter.expire(duration);
            return value;
        });
    }

    /**
     * 获取当前计数器值.
     *
     * @param key 计数器键
     * @return long 当前计数值
     */
    public long getCounterValue(String key) {
        return execute(() -> {
            RAtomicLong counter = getAtomicCounter(key);
            return counter.get();
        });
    }

    /**
     * 删除指定键的计数器.
     *
     * @param key 计数器键
     */
    public void deleteCounter(String key) {
        execute(() -> {
            RAtomicLong counter = getAtomicCounter(key);
            counter.delete();
            return null;
        });
    }

    /**
     * 使用分布式锁执行任务并返回结果.
     *
     * @param lockType  锁的类型
     * @param lockKey   锁的键
     * @param waitTime  等待获取锁的最长时间（可选，默认：3秒）
     * @param leaseTime 租约时间（可选，默认：-1，表示永不过期）
     * @param timeUnit  时间单位（可选，默认：秒(s)）
     * @param supplier  供应者函数式接口，用于执行业务逻辑
     * @param <T>       泛型返回值
     * @return T 执行结果
     */
    public <T> T executeWithLock(RedissonLockType lockType, String lockKey,
        Long waitTime, Long leaseTime, TimeUnit timeUnit, Supplier<T> supplier) {

        long beginMillis = System.currentTimeMillis();
        log.info("Redis分布式锁获取，lockKey={}，开始时间={}", lockKey, DatesUtil.parseDate(beginMillis, DatePattern.NORM_DATETIME_MS_PATTERN));

        final LockConfig lockConfig = this.redisProperty.getLock();

        final RedissonLockType actualLockType = Optional.ofNullable(lockType).orElse(lockConfig.getLockType());
        final Long actualWaitTime = Optional.ofNullable(waitTime).orElse(lockConfig.getWaitTime());
        final Long actualLeaseTime = Optional.ofNullable(leaseTime).orElse(lockConfig.getLeaseTime());
        final TimeUnit actualTimeUnit = Optional.ofNullable(timeUnit).orElse(TimeUnit.SECONDS);

        RLock lock = null;
        try {
            lock = this.getLock(actualLockType, lockKey);

            boolean status = this.tryLock(lock, actualWaitTime, actualLeaseTime, actualTimeUnit);
            if (!status) {
                log.warn("Redis分布式锁获取失败，lockKey={}, cost={}ms", lockKey, System.currentTimeMillis() - beginMillis);
                throw new BizException(BizCodeEnum.GET_LOCK_ERROR, new Object[]{lockKey});
            }

            // 执行业务逻辑
            return supplier.get();
        } finally {
            this.unlock(lock);

            final long endMillis = System.currentTimeMillis();
            log.info("Redis分布式锁释放完成，lockKey={}，结束时间={}，总执行毫秒数={}", lockKey,
                DatesUtil.parseDate(endMillis, DatePattern.NORM_DATETIME_MS_PATTERN), endMillis - beginMillis);
        }
    }

    private <T> T execute(Supplier<T> supplier) {
        try {
            return supplier.get();
        } catch (Exception ex) {
            log.error(ExceptionConstant.EXCEPTION, ex);
            throw new BizException(BizCodeEnum.OPERATE_ERROR);
        }
    }

}